package org.atlantis.grosbeak.lang;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.atlantis.grosbeak.lang.codec.Charsets;

/**
 * 用来获取资源的帮助类。
 * 
 * @author <a href="mailto:seenoevil.cn@gmail.com">SeeNoEvil</a>
 */
public abstract class Resources {

	/**
	 * 查找和参照类同级（即同一个Package下）的所有类，支持Jar文件。 <br>
	 * 如有多个类在同一个<b>Package</b>下：
	 * <ul>
	 * <li>org.atlantis.grosbeak.lang.Resources</li>
	 * <li>org.atlantis.grosbeak.lang.Files</li>
	 * <li>org.atlantis.grosbeak.lang.Strings</li>
	 * <li>org.atlantis.grosbeak.lang.Mirror</li>
	 * <li>org.atlantis.grosbeak.lang.Details</li>
	 * </ul>
	 * 那么当把baseClass设置为Resources.class时就会把org.
	 * atlantis.grosbeak.lang.class下所有的类都找到：
	 * <ul>
	 * <li>Resources.class</li>
	 * <li>Files.class</li>
	 * <li>Strings.class</li>
	 * <li>Mirror.class</li>
	 * <li>Details.class</li>
	 * </ul>
	 * 
	 * @param baseClass
	 *            参照类，参照此类的Package。
	 * @return 和参照类同级的所有类。
	 * 
	 * @see #findClass(String)
	 */
	public static List<Class<?>> findClass(Class<?> baseClass) {
		return findClass(baseClass.getPackage().getName());
	}

	/**
	 * 查找在指定包下的所有类，支持Jar文件。 <br>
	 * 如有多个类在指定<b>Package</b>下：
	 * <ul>
	 * <li>org.atlantis.grosbeak.lang.Resources</li>
	 * <li>org.atlantis.grosbeak.lang.Files</li>
	 * <li>org.atlantis.grosbeak.lang.Strings</li>
	 * <li>org.atlantis.grosbeak.lang.Mirror</li>
	 * <li>org.atlantis.grosbeak.lang.Details</li>
	 * </ul>
	 * 那么当把packageName设置为org.atlantis.grosbeak.lang时就会把org.
	 * atlantis.grosbeak.lang下所有的类都找到：
	 * <ul>
	 * <li>Resources.class</li>
	 * <li>Files.class</li>
	 * <li>Strings.class</li>
	 * <li>Mirror.class</li>
	 * <li>Details.class</li>
	 * </ul>
	 * 
	 * @param packageName
	 *            指定包（如；org.atlantis.grosbeak.lang）。
	 * @return 在指定包下的所有类。
	 */
	public static List<Class<?>> findClass(String packageName) {
		File dir = getPackageFile(packageName);
		if (null == dir)
			return new ArrayList<Class<?>>(0);
		String[] classNames = null;
		String jarPath = getJarPath(dir);
		if (jarPath != null)
			classNames = findInJar(jarPath, packageName);
		else
			classNames = findInClassPath(dir, packageName);
		if (classNames == null)
			return new ArrayList<Class<?>>(0);
		List<Class<?>> list = new ArrayList<Class<?>>(classNames.length);
		for (String className : classNames) {
			try {
				list.add(Class.forName(className));
			} catch (ClassNotFoundException e) {
			}
		}
		return list;
	}

	/**
	 * 根据指定类，在ClassPath下查找该类所在的目录。 <br>
	 * 如：
	 * <ul>
	 * <li>指定类为：org.atlantis.grosbeak.lang.Resources，那么返回的是~/org/atlantis/
	 * grosbeak/lang的目录文件对象。（即指定类的包）</li>
	 * </ul>
	 * 
	 * @param clazz
	 *            参考类，参考此类的Package。
	 * @return 参照类文件所在目录的文件对象。
	 * 
	 * @see #getInClassPath(String base)
	 */
	public static File getClassFile(Class<?> clazz) {
		return getInClassPath(clazz.getName().replace('.', '/') + ".class");
	}

	/**
	 * 根据一个Package的名称在ClassPath下查找Package所对应的目录。<br>
	 * 如：
	 * <ul>
	 * <li>packageName为org.atlantis.grosbeak.lang，那么返回的是~/org/atlantis/grosbeak/
	 * lang的目录文件对象。</li>
	 * </ul>
	 * 
	 * @param packageName
	 *            指定包（如：org.atlantis.grosbeak.lang）。
	 * @return 指定包所对应的目录。
	 * 
	 * @see #getInClassPath(String)
	 */
	public static File getPackageFile(String packageName) {
		return getInClassPath(packageName.replace('.', '/'));
	}

	/**
	 * 在ClassPath根下根据参照类（*.class文件）或指定包（目录）的相对路径来查找类或包的文件对象。
	 * 
	 * @param base
	 *            类或包相对于ClassPath的路径。如：org/atlantis/grosbeak/lang、org/atlantis/grosbeak
	 *            /lang/Resources.class。
	 * @return 类或包的文件对象，如果找不到则返回null。
	 * 
	 * @see ClassLoader#getResources(String)
	 */
	public static File getInClassPath(String base) {
		try {
			Enumeration<URL> urls = new ClassLoader() {
			}.getResources(base);
			File file = null;
			if (null != urls && urls.hasMoreElements()) {
				URL url = urls.nextElement();
				if (null != url) {
					String path = url.getFile();
					path = Charsets.decode(path);
					file = new File(path);
				}
			}
			if (null == file)
				file = Files.find(base);
			if (null == file)
				return null;

			if (file.isDirectory())
				return file;
			return file.getParentFile();
		} catch (IOException e) {
		}
		return null;
	}

	/**
	 * 从Jar文件中查找和参照类同级（即同一Package下）的所有类的全名（全名如：org.atlanti.grosbeak.
	 * lang.Resources）。
	 * 
	 * @param jarPath
	 *            Jar文件的路径（如/home/atlantis/mave-repository/junit/junit4.7/junit-4.7
	 *            .jar）。
	 * @param baseClass
	 *            参照类(如：Resources.class)。
	 * @return 所有和参照类处于同一包 下的类的全名。
	 * 
	 * @see #findInJar(String, String)
	 */
	public static String[] findInJar(String jarPath, Class<?> baseClass) {
		return findInJar(jarPath, baseClass.getPackage().getName());
	}

	/**
	 * 从Jar文件中查找在指定包下的所有类的全名（全名如：org.atlantis.grosbeak.lang.Resources）。
	 * 
	 * @param jarPath
	 *            Jar文件的路径（如/home/atlantis/mave-repository/junit/junit4.7/junit-4.7
	 *            .jar）。
	 * @param packageName
	 *            指定包（如：org.atlantis.grosbeak.lang）。
	 * @return 在指定包下所有的类的全名。
	 * 
	 * @see #findInJar(String, Class)
	 */
	public static String[] findInJar(String jarPath, String packageName) {
		try {
			jarPath = Charsets.decode(jarPath);
			ZipEntry[] entries = Files.findInZip(new ZipFile(jarPath),
					packageName.replace('.', '/') + "/\\w*.class");
			if (null != entries && entries.length > 0) {
				String[] classNames = new String[entries.length];
				for (int i = 0; i < entries.length; i++) {
					String pathName = entries[i].getName();
					classNames[i] = pathName.substring(0,
							pathName.lastIndexOf('.')).replaceAll("[\\\\|/]",
							".");
				}
				return classNames;
			}
		} catch (IOException e) {
		}
		return null;
	}

	/**
	 * 从一个目录中查找和参考类同级（即同一Package下）的所有类的全名（全名如：org.atlantis.grosbeak.lang.
	 * Resources）。
	 * 
	 * @param dir
	 *            要查找类的目录（如：/home/workspace/eclipse/grosbeak/target/classes/org/atlantis
	 *            /grosbeak/lang）。
	 * @param baseClass
	 *            参照类（如：Resources.class）。
	 * @return 目录下所有类的全名。
	 * 
	 * @see #findInClassPath(File, String)
	 */
	public static String[] findInClassPath(File dir, Class<?> baseClass) {
		return findInClassPath(dir, baseClass.getPackage().getName());
	}

	/**
	 * 从一个目录中查找属于指定包的所有类的全名（全名如：org.atlantis.grosbeak.lang.Resources）。
	 * 
	 * @param dir
	 *            要查找类的目录（如：/home/workspace/eclipse/grosbeak/target/classes/org/atlantis
	 *            /grosbeak/lang）。
	 * @param packageName
	 *            指定包（如：org.atlantis.grosbeak.lang）。
	 * @return 目录下所有类的全名。
	 * 
	 * @see #findInClassPath(File, Class)
	 */
	public static String[] findInClassPath(File dir, String packageName) {
		File[] files = dir.listFiles(new FileFilter() {
			public boolean accept(File pathname) {
				return pathname.getName().endsWith(".class");
			}
		});
		if (null != files && files.length > 0) {
			String[] classNames = new String[files.length];
			for (int i = 0; i < files.length; i++) {
				String fileName = files[i].getName();
				String classShortName = fileName.substring(0,
						fileName.length() - 6);
				classNames[i] = packageName + "." + classShortName;
			}
			return classNames;
		}
		return null;
	}

	/**
	 * 从指定目录dir中找到Jar文件的路径。
	 * 
	 * @param dir
	 *            指定目录。
	 * @return Jar文件的路径。
	 */
	public static String getJarPath(File dir) {
		String path = dir.getAbsolutePath();
		/*
		 * 当使用ClassLoader的getResource方法查找Jar包中的类文件时Jar会以!结尾。
		 * 如：jar:file:/D:/maven
		 * -repository/junit/junit/4.7/junit-4.7.jar!/org/junit/Test.class
		 */
		int posBegin = path.indexOf("file:") + 5;
		int posEnd = path.lastIndexOf("!");
		if (posBegin > 0 && (posEnd - posBegin) > 0)
			return path.substring(posBegin, posEnd);
		return null;
	}
}
